﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace JasLib.Threading
{
    /// <summary>
    /// This offers two advantages over SynchronizationContext:
    /// (1) Rarely-used Send() functionality is automatically implemented as a wrapper around Post().
    /// (2) The base class calls Send() synchronously on the calling thread, which is incorrect.
    /// Suppose you want to Send() to the MTA thread pool to execute an MTA-only component (like the WMI classes),
    /// but you're running on an STA thread; you will have a crash.
    /// </summary>
    public class ThreadPoolSynchronizationContext : SynchronizationContext
    {
        /**********************************************************************************/
        /// <summary>
        /// This is identical in function to the GetAwaiter() pattern's IsCompleted property.
        /// </summary>
        public virtual bool CanSendSynchronously
        {
            get
            {
                return Thread.CurrentThread.IsThreadPoolThread;
            }
        }

        /**********************************************************************************/
        public sealed override void Send(SendOrPostCallback d, object state)
        {
            if (this.CanSendSynchronously)
            {
                d(state);
            }
            else
            {
                using (var handledEvent = new ManualResetEventSlim(false))
                {
                    Post(SendOrPostCallback_BlockingWrapper, Tuple.Create(d, state, handledEvent));
                    handledEvent.Wait();
                }
            }
            return;
        }

        /**********************************************************************************/
        private static void SendOrPostCallback_BlockingWrapper(object state)
        {
            var innerCallback = (state as Tuple<SendOrPostCallback, object, ManualResetEventSlim>);
            try
            {
                innerCallback.Item1(innerCallback.Item2);
            }
            finally
            {
                innerCallback.Item3.Set();
            }
            return;
        }
    }
}
